Loading...
 

Static MorphIT

Static MorphIT

In the normal MorphIT mode each connected browser is assigned exactly one ClassiX instance until either browser or ClassiX is closed. If no ClassiX is available, the client is put into a queue and the user has to wait until an instance is restarted or released again.

The MorphIT server offers a static mode, in which previously exported static views with limited interactivity can be used even without a connected ClassiX. This relieves the server of simple page requests and does not limit the access to static content by the number of ClassiX processes.

Functionality

Before setting up the static mode, it is important to understand how it works, as the technical implementation and security aspects have some implications for the application.

Static Views

The MorphIT client detects that the static mode is active when loading. In this case no web socket connection to the MorphIT server is established (the server prevents this in static mode anyway), but an HTTP GET request with the path /view/0 is triggered. The server answers with the initial view, which is treated by the client as if it came from a ClassiX instance and represents the user interface.

In addition to the regular views, values, options, these static views contain an event map that defines the action behind each triggerable event. Normally a SELECT-event leads to a new view being opened. Each view is assigned an ID and each view can be requested via /view/ID. For navigation between views no running ClassiX instance is necessary, a running MorphIT server that delivers the statically exported views is sufficient.

Webservice Events

195482

With purely static views, the static mode resembles primitive HTML pages that are linked together. Since, at the latest for login and thus the change to the dynamic mode (linked to a ClassiX instance), interactivity with ClassiX is required that cannot be exported statically, any interactivity on the static pages is traced back to HTTP POST web service requests. The web service requests are served by special ClassiX instances (number configurable), which are specifically reserved by the MorphIT server and are not coupled with regular MorphIT connections. The application can be easily scaled by the number of web service instances to be used.

Web service requests are sent by the MorphIT server via the connected web socket to the ClassiX instance and there they are handled via the regular web service interface and the response is sent via the web socket to the server and then as HTTP response to the client.

If the static views were exported in multiple languages, the client sends the currently activated language(x-morphit-language) and the UTC offset(x-morphit-utc-offset) in the HTTP header with every web service request. These headers are then also available in ClassiX, so that the web service can answer in the language of the client and convert data correctly.

WebWidgets

The webwidgetCommunication service used for web widgets recognises that the client is running in static mode and automatically sends all web widget messages as web service requests to the server. To support a web widget in static mode, ClassiX has to react to the corresponding web service requests in the same way as the web widget.

To decide which data should be displayed in the WebWidget, the export slot can be set to wwPrefix=pre when exporting in the WebWidget. This would prefix all WebWidget requests of this widget with the prefix PRE_ (example: PRE_GET_ROOT_POST). By using different prefixes, several WebWidgets of the same type can be supported, which work on different data.

Widgets

Regular widgets that do not open a view (example: login button) are also implemented through web service requests. In order for this to work automatically, the widgets have to be marked accordingly via the export slot and ClassiX has to respond to the request correctly. More about this here.

Direct links

In dynamic mode, a direct link first establishes a connection to a ClassiX and then executes the _AUTO message in this ClassiX. Since in static mode only authenticated users are allowed to receive a ClassiX instance, a special web service request is made beforehand VALIDATE_AUTO_POST, in which the web service instance can check beforehand whether the passed token/URL are valid and a ClassiX should be assigned to the client (response type: "dynamic"), or not (response type: "error", other).

If the request is valid, then VALIDATE_AUTO_POST should return the same JSON that was passed in the body of the request and additionally set the type field in the JSON to "dynamic". The JSON body of the request object has the following format:

Field Type Description
car string The path of the auto-message / name of the message. (Example: login for LOGIN_AUTO)
path string The path on which the direct link was triggered. Is usually "/" and can be neglected.
params Object Possible parameters of the direct link (login token, ...) are transferred as fields in this object.

Flow chart - direct link
Flow chart - direct link

Answer types

For web service requests through regular widgets and direct links, special responses are defined which are interpreted by the client and thus allow more interaction.

Error

Errors are displayed by the MorphIT client in an error dialogue box which can be closed with OK and the current view is not changed. After clicking away the error, work can be continued normally.

Field Type Description
type string The string "error
errorId string Optional: If a translation into the current language for the error is stored in the translation files of MorphIT, the translation key can be specified here and the client will display the translated error message. (Example: "server.no_ws_available")
error, error_message string English error text that is displayed if the error does not define an errorId .

Attention

This response is handled by the client in a similar way to error, but the displayed dialogue box can be better designed. Due to the stateless communication, the ClassiX cannot respond to which button was pressed.

Field Type Description
type string

The string "attention

parameters Object Display parameters
parameters.text string The text to be displayed
parameters.level string Optional: The notification level of the dialogue. Allowed values: error, info, warning, exception, question. Default: warning
parameters.timeout integrity Optional: If the dialogue should close automatically after a certain waiting time, then the waiting time can be specified here in seconds.
parameters.buttons Array Optional: Here you can define a list of buttons to be displayed in the dialogue. The buttons are specified as translation-Ids relative to dialog.button. (Example: ["ok", "cancel"]). If an empty array is specified, then no button is shown in the dialogue, the dialogue cannot be closed by the user.

Login / Dynamic

If ClassiX responds with a login or dynamic type response, the MorphIT server automatically reserves a unique websocket endpoint for the client and places the endpoint ID in the response before it is forwarded to the client. The client then automatically connects to this endpoint, which allows exactly one connection and then becomes invalid. The server accepts the websocket connection and connects the client to a free ClassiX instance, or queues it. The client then switches to dynamic mode.

Field Type Description
type string The string "login". As of MorphIT 3.16.3 alternatively also "dynamic".
endpoint string

This string is automatically put into the response by the MorphIT server and is used by the client to establish a connection to the ClassiX. The fact that only a client authorised by ClassiX knows this web socket ID prevents that attackers can use server resources indefinitely by establishing socket connections and thus disturb authenticated users. The ID is a randomly generated SHA-256 hash.

In the case that a dedicated ClassiX instance (and not a pre-launched one) is to be assigned, then the CX_MORPHIT_NONCE returned by RequestMorphITBinding or the endpoint returned by request_binding must be put in here. This value is then not overwritten by the MorphIT server.

params Object

Optional: If the MorphIT client is to trigger an AUTO message directly on its connected instance, this can be defined here with its parameters. In addition to the mandatory field auto, any parameters can be passed to the event handler of the _AUTO message via the params object.

The _AUTO message following from the client is not validated beforehand by a VALIDATE_AUTO_POST message.

auto string

Optional: If the MorphIT client is to trigger an AUTO message immediately after establishing a connection to the newly assigned ClassiX instance, then this can be specified here (without the _AUTO suffix). The name specified here is converted to uppercase letters and the _AUTO suffix is added to obtain the message name.


Important: Since this message is triggered after a ClassiX instance has been assigned, no VALIDATE_AUTO_POST web service is triggered for this AUTO-message to validate the message.

params.(paramName) string Optional: any parameter values to be passed in the body of the client in the _AUTO request.
If the response type is "login", the client automatically sends the same request parameters that were passed in this web service call (usually the login data). In this case, these do not have to be explicitly set in the response.
url
4.1.4
string Optional: If set, the client is prompted to switch to the specified URL before the web socket connection is established. This change does not trigger any navigation, but has the effect that all resources are requested relative to this new URL. No port or domain change can be made via this URL.

To enable a full login, there is a small difference between login and dynamic. If the web service responds with login, the MorphIT client must resend the request parameters of the corresponding web service request (usually the login data) in the auto parameters. This way the assigned ClassiX instance can perform a complete login.

The flowcharts of static login and static direct link hardly differ.

Flowchart - Static login
Flowchart - Static login

Redirect

With this response ClassiX can make the client load a certain static view (can also be a view alias ). This way the client can be redirected to a certain view in response to a web service request instead of giving the user feedback via attention/error.

Field Type Description
type string The string"redirect
view string The view to be loaded (Ex: "0"). Can also be a view alias like "login".

UpdateView

4.1.0

This response type allows you to write an interactive application even in static mode.
All fields of this message are optional and can be combined as desired. Widgets to be modified are always referenced in this message by their parameter names, not by the widget ID. In order for this message to be processed correctly, it is important that the same parameter names are not assigned more than once within a view.

Field Type Description
type string The string"update_view
redirect string

The view to be loaded. This can either be a view index (0-...) or the name of an alias view. If this field is set, then the specified view is loaded first and the remaining modifications of the view then refer to the newly loaded view.
If redirect is not set, then the current view of the client is adjusted.

If redirect is set, then all data entered by the user will be replaced by the data of the new view. This can be used to explicitly delete the data entered by the user by reloading the same view via redirect.

validation Object This object sets the validation state of the widgets. The parameter names of the widgets are the keys and the values are JSON objects of the form {"level", "text"}. Valid levels are: "info", "warning", "error".
values Object This object can overwrite the values of individual widgets. Again, the parameter names of the widgets are the keys and the values are the values of the widgets to be set. The format of the values depends on the widget type and has the same format as it was passed to ClassiX in the body of the web service request.
views Object

This object can override the display of individual widgets. Again, the parameter names of the widgets are the keys and the values are objects whose properties override the views of the widgets. The entire view cannot be described here.
These are the most important keys:

Field Type Description
type string The widget type
left, top, width, height string The positioning of the widget. Data must have the "px" suffix (e.g. "123px")
title string The text to be displayed for groups, prompts, items, windows, checkboxes, ...
tooltip string The tooltip to be displayed
locked bool Corresponds to the IV flag LOCKED
hidden bool Corresponds to the IV flag HIDDEN
readonly bool Corresponds to the IV flag VIEW_ONLY
focus string If this field is set, the widget is focused with the specified parameter name. However, this only works if there is no validation state in the view to be loaded, otherwise the validation info box will be focused.
dialogue Object An object with the properties level ("info", "warning", "error") and text. If this object is set, a dialogue with the corresponding text and level is displayed on the updated view. The user can only confirm this dialogue by clicking OK.
customization
4.1.4
Object

In this object, adjustments can be defined that are retained for the entire duration of the static MorphIT session and applied to all future views. This object has a similar structure as the update_view message itself and allows the following fields.

  • views - the widget properties that cannot be changed by the user
  • values - The widget values
  • options - global properties, like the project name and text in the header

The values to be set can also be stored in the response in several languages (prerequisite: multilingual static export). For this purpose an object with the following structure is used instead of a simple string:

{"t":{"de":"deutscher Text", "en":"english text", ...}}

Example:

{









}

Example:
The following JSON sets the validation state for two widgets, fills the email widget with a dummy text and unchecks the checkbox and unlocks a button.

{ "type":"update_view", "validation": { "email":{ "level": "error", "text": "Email must be filled out!" }, "comment": { "level": "info", "text": "A comment may be helpful" } }, "values": { "email":"example@host.tld", "confirmCheckbox": false }, "views": { "resetButton": { "locked": false } } }

Example:
The following JSON redirects the client to the login page, focuses the widget with the parameter name "userID" and displays a dialog informing the user that the registration was successful.

{ "type":"update_view", "redirect":"login", "focus":"userID", "dialog":{ "level":"info", "text":"Registration successful" } }

Static export

Before the static mode can be used, the static views must first be created. For this purpose, MorphIT offers an automated crawler that automatically walks through all accessible views, links them with each other and then downloads them. The crawler is accessible via the DesignMode menu(only active if design_mode_enabled=true is set in the configuration) (button:"Dump views"). During the export, the MorphIT server must still be running in non-static mode(static.enabled=false), otherwise the button is not displayed either.

This first opens the export overlay, which prevents interaction with the interface during export. Here you will also find settings for the export, a button to start the export and a log, which allows a diagnosis in case of export errors.

Preparation

Before exporting, you need to understand how static export works. Starting from the start-view, unknown events are triggered until the export knows which event leads to which view. Events of HIDDEN, LOCKED or NON_SELECTABLE widgets are ignored. If ClassiX triggers an error or attention during the export, the export is aborted. The automatic export does not fill in any fields, it only triggers SELECT. To export static views with interactivity, some widgets have to be provided with additional information by setting special slot values for the static export.

Since the crawler triggers each event once from each view, the duration of the export increases very quickly with the increasing number of events/views. In the export overlay there are settings that visualise the export better, but slow it down considerably for longer runs. The default settings guarantee the fastest export.

Widget Slots

If the SELECT event on the widget should lead to a web service request, then the slot key action must be defined on the triggering widget and parameters on the parameters to be transmitted. The following is an example of a login mask:

String(userStr, 0, 0, 100) [ INITIALIZE: "parameter=username" Widget Put(morphIt.externalID) ] String(passStr, PASSWORD, 0, 10, 100) [ INITIALIZE: "parameter=password" Widget Put(morphIt.externalID) ] Button(login, 0, 20, 100, 8) [ INITIALIZE: "action=login_static" Widget Put(morphIt.externalID) SELECT: DoRegularLogin ]

When exporting, the crawler will not trigger the SELECT event on the button, but will note in the view that a web service is to be triggered when a SELECT occurs. The parameter fields are transferred as JSON objects in the HTTP body. Query parameters that are set in the client are also transferred as query parameters in the HTTP request object and are available in the web service.
If the MorphIT server runs under HTTPS (recommended!), then the web service requests are also made over HTTPS and their content is therefore encrypted and cannot be read by third parties. This web service must be programmed in ClassiX afterwards:

Msg(LOGIN_STATIC_POST) // will be send to webservice instance first LOGIN_STATIC_POST: { LocalVar(reqjs, name, pw) Call(GetBody) jsonTools::LoadFromString -> reqjs //parse body reqjs Copy(username) -> name reqjs Copy(password) -> pw // result if login doesn't match "{\"type\":\"error\", \"error\":\"Wrong username/password passed!\"}" jsonTools::LoadFromString -> result // very simplified login check name "admin" = pw "pass" = & if { // login successful CreateTransObject(CX_JSON_OBJECT) -> result "login" result Put(type) //response type = "login" "login_static" result Put(auto) //client should trigger LOGIN_STATIC_AUTO } // return response result ReturnStack } Msg(LOGIN_STATIC_AUTO) // will be send to assigned instance LOGIN_STATIC_AUTO: { LocalVar(loginjs, name, pw) jsonTools::LoadFromString -> loginjs // Because we have answered with type="login" we get the same login parameters again // If we had answered with type="dynamic" then we wouldn't have access to the same parameters loginjs Copy(username) -> name loginjs Copy(password) -> pw // recheck login to be safe name "admin" = pw "pass" = & if { // actually login user with that user/password ... } else { // We can open an attention here, because this is the connected ClassiX instance "Invalid login data!" Attention(,ERROR) TerminateApp } }
Caution: It is important to be aware that in the example shown the message LOGIN_STATIC_POST is processed by the WebService instance. The message LOGIN_STATIC_AUTO is processed by the later assigned ClassiX instance (not the same one!) Setting a module variable in LOGIN_STATIC_POST to keep the login state is therefore an error and will not work.
If information, like a login token, has to be exchanged between the web service instance and the later ClassiX instance, these shared data have to be written into the database.

The login example given here is for illustration purposes only. In the concrete implementation, care should be taken that the login token can only be used once and expires after a finite time, so that valid (unused) tokens cannot be guessed by an attacker by trial and error. This time should not be chosen too short, if more users use the system than ClassiX processes can be started. The client would be put into a queue after login and would use the login token only when a ClassiX instance becomes free. If the token expires in the meantime, the user has to log in again. To prevent this, a timeout of about 5-10 minutes should be selected.

The process of a static login (with other messages) is illustrated in the following flow chart.

Flowchart - Static login
Flowchart - Static login

Widgets whose SELECT event not only leads to a new view and cannot be mapped to web services can be hidden in the exported view as follows:

[ INITIALIZE: "hidden=true" Widget Put(morphIt.externalID) ]

Slot values

For export purposes, widgets can be provided with additional semantics via the slot morphIt.externalID. The slot is a string with the syntax: key=value;key=value;key=value
Keys are not case-sensitive.

The following keys are defined:

Key Value Description
action Path The path of the Web service request to be sent when the SELECT event is triggered on the widget.
action@ID Path The path of the web service request to be sent to the widget part with the id ID when the SELECT event is triggered. This syntax is analogous to SELECT@ID from the link widget, since it refers to exactly the sublinks of the link widget.
captcha
4.1.1
Path This is used to mark a widget for static export as CAPTCHA and the specified path(s) (as a comma-separated list) determines which web services are protected by the CAPTCHA. Widgets that define one of these paths in action are automatically locked and unlocked by the CAPTCHA once the CAPTCHA has been resolved.
columnAsId
4.5.0
Column index ( ≥0 ) If ObjectComboboxes are defined as parameters for Webservice Events(action), you can specify that the combo box should send the text of the i-th column as its value and not the id of the entry.
eventSource
4.5.1
Name

If a SELECT event is triggered on a widget with action, then the triggering widget can be transmitted via eventSource as a parameter in the web service request. The name specified with eventSource is the parameter name and can be used several times within a window, which is transmitted and the parameter value is the field from id or parameter of the widget.

If, as in the example below, two buttons with the following slot values are created, then both buttons would trigger the same webservice and depending on which button was clicked, either trigger=btn1 or trigger=btn2 is passed as parameter to the webservice.

hidden true/false

true - the widget is not displayed in the exported static view and does not export events.
false - A normally invisible widget is exported as a visible widget in static export and events can be executed on it. Such widgets must not define a SELECT event that returns a new view. However, a webservice action is allowed.

id
4.1.3
Name Similar to parameter, you can refer to a widget with this name in an update_view response. In contrast to parameter, the values of these widgets are not transmitted with action requests.
indexAsId
4.5.0
true If ObjectComboboxes are defined as parameters for webservice events(action), you can specify that the combo box should send the index (≥0) of the selected entry (as string) as its value and not the id of the entry.
locked
4.1.1
true/false true - the widget is locked in the exported static view and does not export events.
false - A normally locked widget is exported in static export as a regular widget and events can be executed on it. Such widgets must not define a SELECT event that returns a new view. However, a webservice action is allowed.
parameters Name If a SELECT is triggered in the window of this widget on a widget with a defined action, the value of this widget is sent along in the JSON body of the web service request under the key Name. Widgets which do not set parameters are not automatically transmitted with action requests. In the update_view response you can refer to the widgets of the view by this name.
wwPrefix prefix Only evaluated by WebWidgets and contains a message prefix that precedes the path of each Web service request by the WebWidget (separated by underscore)
long
4.1.0
Language abbreviation This value should be used to mark the menu items that can be used to change the language in the machine. If the static export finds at least 2 such buttons, then the views are exported language-independently and the static export additionally generates a languages.json and for each language an xx_lang.json, which are stored together with the views in the same directory. With the help of these translation files, the views can then be translated into any language.
The common language abbreviations de, en, fr, es, ... should be used.

WebWidgets

In order for web widgets in static mode to work as well, _POST messages with the same name (in the module) must be handled in addition to the _SOCKET messages of the web widgets that have been usual up to now. The WebWidget should not be opened for processing the messages in order to avoid bringing a state into the web service instance and the instance cannot rely on the fact that the WebWidgets have been filled with data by a previous event (each request must be self-contained).

If several WebWidgets of the same type (with different data/settings) are to be used in a static application, the wwPrefix key can be used to define a prefix that precedes each message of this WebWidge so that the message indicates which WebWidge is requesting data. Example (Organigram):

Web(...) [ INITIALIZE: "wwPrefix=abc" Widget Put(morphIt.externalID) ] Web(...) [ INITIALIZE: "wwPrefix=test" Widget Put(morphIt.externalID) ] Msg(ABC_GET_ROOT, TEST_GET_ROOT) ABC_GET_ROOT: //... TEST_GET_ROOT: //...

CAPTCHAs

4.1.1

MorphIT offers the possibility to easily integrate reCAPTCHA in static mode to prevent access to certain web services by bots and thus possible DOS attacks. The user must first be identified by CAPTCHA as a real user before the web service request is passed on to ClassiX. As CAPTCHAS serve to protect access to ClassiX resources against unauthorized access, they are only implemented in static mode, as an existing MorphIT connection assumes that the user is already authorized.

Requirements:

  • Google account
  • MorphIT server and MorphIT clients have an Internet connection

To integrate a CAPTCHA in MorphIT, a Google account must first be created and a reCAPTCHA (version 2) registered for the server. All hostnames/domains through which the MorphIT server should be accessible for clients should be entered as hostnames. For a purely local use localhost must be entered. With the registration of the reCAPTCHA you receive a site key and a secret key. These keys must be entered in the server configuration under static.captcha.secret_key and static.captcha.site_key . Now the server is prepared.

Before the static export, a placeholder widget (e.g. a button) must now be defined for the CAPTCHA, which is replaced by the CAPTCHA in the exported state. The widget can be HIDDEN in normal mode and can be made visible only for static export via the export slot hidden=false. The size of the widget is ignored, because the static export uses the optimal size (300 x 75 pixels) for this.

Example code:

Button(captchaBtn, HIDDEN, 100, 11, 200, 7, "CAPTCHA") [ INITIALIZE: "captcha=CREATE_USER_CLOUD_ACCOUNT_STATIC;hidden=false" Widget Put(morphIt.externalID) ] Button(OKBtn, LOCKED, 200, 11, 200, 7, T("&Speichern", "&Save")) [ INITIALIZE: "call_to_action" Widget Put(morphIt.drawingID) "action=CREATE_USER_CLOUD_ACCOUNT_STATIC;locked=false" Widget Put(morphIt.externalID) SELECT: SaveObject Drop ] Attach(OKBtn, RIGHT, 15) Attach(captchaBtn, OKBtn, RIGHT, OPPOSITE, 100)

Declaration:

  1. The OKBtn triggers the web service CREATE_USER_CLOUD_ACCOUNT_STATIC in static mode, therefore captcha=CREATE_USER_CLOUD_ACCOUNT_STATIC must also be set in the captcha widget. Thereby the CAPTCHA is linked to the button.
  2. The OKBtn is normally locked and is only unlocked by the input validation (ALTERED). Because the static mode does not support an ALTERED event, the button must always be unlocked, this is done with locked=false.
  3. So that the captcha widget does not disturb in regular operation, it is HIDDEN and is made visible for static export via hidden=false.

If the interface contains at least one CAPTCHA during static export, then an additional captcha_webservices.json is downloaded together with the remaining views when the export is completed. This file should be exported together with the views into the same directory and is used by the MorphIT server to determine which webservice requests must be verified by a CAPTCHA and which not. If this file is missing, then no CAPTCHAs are verified by the server and all requests can theoretically be executed without solving a captcha.

reCAPTCHA delivers a token upon successful confirmation, which is transmitted in the x-morphit-captcha HTTP header in the web service request to the MorphIT server and can be verified once by the server. Webservice requests for webservices declared in captcha_webservices.json and sent with an invalid token or missing token are rejected by the MorphIT server with an error message and ClassiX does not notice these requests.

Result:

Captcha

Direct links

If direct links are to be supported in static mode, the message VALIDATE_AUTO_POST must be defined as described here .

Alias Views

If the client should be able to jump to a certain mask in static mode (before login) via a certain URL (example: open login mask directly), then after the static export a redirection from a freely selectable identifier to the view to be opened must be set up for the view to be reached.

The easiest way to generate this redirection is via the design mode. If the server is started instatic mode(static.enabled=true), the button"Alias view" appears in the design mode menu. This button first asks for an alias name for the current view and then generates a redirection file which the browser automatically downloads. This file must be placed in the same directory as the automatically exported views of the static export for the alias to work.

For example, if you navigate to the login mask and set the alias name to login, the browser will download a login_view.json, which contains a redirection to the actual view. If you copy this file into the directory from which the server loads the views, you can jump directly to the login mask in the client using the URL query ?view=login.

If the ID of the view is known, you can also jump to view 9_view.json via ?view=9. Since the IDs depend on the order of the events in the export process, it is recommended and more memorable to assign alias names for them. The fact that the IDs can change also means that the redirection files potentially have to be recreated (or at least checked) after each export.

Script-controlled export

4.1.3

The script-controlled MorphIT export is a possibility to further automate the static export and to export even more complicated applications. In this case an export script is dropped into the export overlay (a JavaScript file) before the export and this script can control the export to a certain extent.

The script can react to events during the export and influence the export as follows:

  • Evaluate views & cancel export with error message
  • Trigger events on the current view and continue the export on another view
  • Display simple dialogues
  • Examine the current export status and define alias views

The following example script is a complex example of what is currently possible with the export script. The script exports two applications one after the other and defines alias views so that the alias views can be used to navigate between the individual applications later.

This script:

  1. Checks whether the correct application has started using the MorphIT properties before export(onExportStart)
  2. Defines 2 alias views after export(onExplorationEnd) by checking which views were reached by clicking on certain widgets
  3. Represents a dialogue that gives instructions to the user
  4. Closes the currently connected ClassiX application and confirms the confirmation request(onAttention)
  5. Checks, using the MorphIT properties, whether the new instance is the expected one.
  6. Defines an alias for this new view and continues the export from there.

Code: (export-script.js)

/** This script is the export script for the cloud application. * It will: * - define the alias views 'login', 'register' and 'login_direct' * - guide the user to correctly export the regular portal login views and the product login view */ events.onExportStart = function(firstView) { var properties = firstView.view.options.properties; if (properties.project !== 'classix.cloud' || properties.project_type !== undefined) { throw new Error("The selected export script is incompatible with the currently connected ClassiX instance! This export script has been written for classix.cloud portal application!"); } }; // Return true or a promise, which resolves to true to rerun the exploration. // The passed view will be an ActiveView on which an event can be triggered. All other views are accessible through exportContext.view(id) events.onExplorationEnd = function(lastView) { // Define login alias var initialView = exportContext.view(0); var loginItemId = initialView.findWidget(function(widget) { return _.get(widget, 'slots.number') === 'UserIcon'; }); var loginViewId = initialView.findEvent(function(event) { return event.id === loginItemId; }).action.view; exportContext.defineAlias(loginViewId, 'login'); // Define register alias var registerViewId = exportContext.view(loginViewId).findEvent(function(event) { return event.event === 'SELECT@new'; // This is how we detect the register link }).action.view; exportContext.defineAlias(registerViewId, 'register'); ////////////// // Now prepare changing the application to export the product login view ////////////// // Register an attention handler here because the ClassiX instance will want to know whether we really want to shut it down // and we don't want to error there and simply confirm this dialog. var oldAttentionHandler = events.onAttention; events.onAttention = function(attention) { attention.clickButton(attention.buttons[0]); events.onAttention = oldAttentionHandler; }; // Show a dialog with further instructions for the user before closing the application showDialog('Information', "The export of the cloud portal views has been completed and ClassiX closed.\n" + "Please start the cloud product login application to continue exporting the product login views."); var controlWin = lastView.findWidget(function(widget) { return widget.control_window === true; }); return controlWin.close().then(function(currentView) { // The promise gets completed as soon as the client reconnects to the next ClassiX instance. // The view hasn't yet been processed by the static export and has thus no id. // We set the project_type property to 'client_login' to mark the expected application as such if (currentView.view.options.properties.project_type !== 'client_login') { throw new Error("Export script expected a different application to connect to. Client login application hasn't been recognized."); } hideDialog(); // The next time exploration has completed, we are done events.onExplorationEnd = function() { return false; }; // Define an alias for this view exportContext.defineAlias(exportContext.nextViewId(), 'login_direct'); // continue exploration from this new view return true; }); };

script events:

onExportStart(activeView :ActiveView) : { bool | Promise }

This function is called before the export with the currently loaded view. The function can trigger events and thus change the initial view. When the function is finished and the export is to be started, it should return true to tell the export that the active view has changed and the view must be reloaded (via load_view message). If it returns false , then the export assumes that the view has not been changed.
If the function works asynchronously (loads views), it can also return a promise to true or false. The export then waits for the asynchronous operation to complete before starting the export.

The export can be stopped completely by throwing an error in this function. The error is displayed in the export status window and the export is not started.

onAfterNormalization(normalizedView :View, originialView) : View
4.5.0

The views are normalised by the export process so that identical views can be recognised as identical despite different widget IDs. This event function is called after each such normalisation step. The function must return the new view to be used as the result of the normalisation. The normalization process can be adjusted by the export script if necessary. normalizedView is the result of the normalization of originalView ({views,values,windows,options}) and should be returned by the function if no completely new view is created. In multilingual applications this function is called once with the regular view and once with an automatically generated multilingual view.

onExplorationEnd(activeView :ActiveView) : { bool | Promise }

This function is called by the export as soon as all events have been tried in all views and the results of the events have been stored in the views. Within this function the script can again trigger events, change views, define aliases and edit views. As soon as this is finished, the function should return a bool (synchronous) or promise to bool (asynchronous). true means that the export should be continued from the now active view, false means that the export is finished and the views should be downloaded. If an error is thrown in this function, the export is aborted without downloading the views and the error message is logged in the status window.

onAttention(attention :ScriptAttention)

This function is called as soon as ClassiX opens an Attention or a dialogue box. By defining this function, the default behaviour can be overridden, which interprets the dialogue as an error, closes it and aborts the export. The attention can be closed again with the passed object.

Script classes:

ActiveView

Represents the currently loaded view. This object contains the following fields:

  • view - The actual view object {views,values,windows,options} of the current view
  • id - The id of this view
  • idMap - An object that represents a map from {exportId -> widgetId}, where the export-Id is the id specified in the export slot under id or parameter.

The class defines the following methods:

  • defineAlias(name:string) - For the active view an alias with the given name is stored in the current ExportContext, which is downloaded together with the views after the export.
  • widget(exportId:string) - If the idMap contains an entry for the given exportId, then the ScriptWidget object for that widget will be returned.
  • findWidget(predicate:(widget,id)=>bool) - If the passed function returns true for a widget, then this widget is returned as ScriptWidget. The widget parameter of predicate are the objects from view.views.
ScriptWidget

Represents a widget in the currently active view. This class is only there to make it easier to trigger events on widgets and this only works if the view from which this ScriptWidget originates is still the active view.
fields:

  • id - The id of the widget

methods:

  • select() - Triggers the SELECT event on the widget and returns a Promise to the following ActiveView.
  • close() - Triggers the CLOSE event on the widget and returns a Promise to the following ActiveView.
ScriptAttention

Represents an attention or dialogue box opened by ClassiX
fields:

  • id - The id of the web socket message
  • text - The text to be displayed
  • level - the level of attention (info,warning,error,exception,question)
  • timeout - The timeout of the dialogue in seconds (if available)
  • buttons - An array of button-ids of the buttons that can be pressed

methods:

  • clickButton(buttonId) - The attention is closed by clicking on the button with the given Id This method returns immediately and not only after the dialogue is completely closed.

View

Represents a normalised view found by the export process. This class has the following fields:

  • view - The actual view object {views,values,windows,options}
  • map - An array of view widget id's used to translate between the original id's and the normalised id's The index in the array corresponds to the normalised Id and the value to the original Id.
  • eventMap - An object of the form {eventName -> {id -> {event}}} which contains all events found in the view
  • languageEvents - An array with one event object for each language accessible from the view. In single language applications this is an empty array.
  • localized - If it is a multilingual application, this field is set in an automatically generated multilingual view and contains the original monolingual view.

methods:

  • findWidget(predicate:(widget,id)=>bool) - Just like ActiveView, this function allows you to search for a specific widget in the view, except that the widget id is returned as the result.
  • findEvent(predicate:(event)=>bool) - This can be used to search the event objects of the view ({id,event,action}) for a specific event. The event object is returned as a result.

Procedure

The page from which the export is started is the initial view (view 0), which is later delivered by the server as the start page. So it is quite possible to navigate to a certain mask/module before the export, which then represents the entry point for the static mode. In this way a static login page can be generated, which contains only a login button, although the main application offers much more functionality at startup.

Starting from the initial view, it reads in all triggerable events (only SELECT and CLOSE for visible, unlocked widgets) and triggers a yet unknown event on the current view and remembers the view returned as result of ClassiX. The export tries to identify identical views by normalisation so that the export does not run endlessly. If the export seems to run endlessly and constantly detects new views, then the views should be checked for differences. Any difference in views, values, options will cause the views to be considered different. Because of this, the export effort increases by a factor of 2 if you work a lot with FLOAT windows, because then every view has one with open and one with closed FLOAT windows. With MODAL windows the problem is not so dramatic, because you can only trigger the events of the modal window from the additional views.

The progress bar shows how many of the known events have already been triggered and linked to a follow-up view. Should ClassiX deliver an attention or an error during the process, the crawler stops and stops the export. By means of the log it can be traced which event has triggered the error and possibly has to be excluded from the export.

Once the export is complete, all views are downloaded as individual .json files. If only (k)one file is downloaded, you may need to adjust the download settings of your browser. The downloaded files must then be moved to the directory configured as static.path (default: ${projectPath}/data/views).

4.1.0:
Due to the download limit of 10 files, the views are downloaded as .zip, which must be unpacked into the corresponding target directory.

The browser, the used ClassiX instance and the server should now be closed. Now the server configuration(static.enabled) has to be adjusted and the server has to be restarted.

Multilingual static export

The static export looks for widgets with the lang attribute on every view it reaches. If two such elements are found, then the view is exported multilingually, which works as follows:

For each view reached, all languages are clicked through and the views sent by ClassiX are compared with each other. All character strings that differ between the languages are replaced by a translation ID. This ID is the index to the translation table of the respective language. For each accessible language such a table is created, which contains all language-specific character strings. If translation tables have been generated, they are downloaded in addition to the views as xx_lang.json together with a languages.json containing the list of all supported languages.

Server configuration

/config/custom/config.js

Name Type Description
static.enabled bool If true, regular websocket connections are disabled and the server provides static views. In addition, ClassiX instances are also reserved as web service instances. (Default: false)
static.path string The path under which the server expects the exported views. (Default: ${projectPath}/data/views)
static.login_timeout integrity The time in milliseconds that a reserved web socket remains reserved for the client after a login response before it expires. The socket only expires if the client does not establish the connection, which is normally only the case if the browser is closed at that moment. (Default: 1 minute)
This duration gives a potential attacker time to guess a valid web socket ID.
static.webservice.enabled
4.6.1
bool Specifies whether the server should activate the web service interface and reserve ClassiX instances as web service. (Default: Same value as static.enabled)
static.webservice.endpoint string The end point at which the server should receive web service requests. (Default: /morphit/webservice)
static.webservice.instances integer | array

The number of Web service instances that the server should reserve. This allows the web service to be scaled as required if the requests are answered too slowly. (Default: 1)
4.2.0, the instances can be configured more precisely using an array.

static.webservice.methods
4.12.0
ArraySpecifies which HTTP methods (GET, POST, PUT, DELETE, ...) and ClassiX Web Service instances are to be forwarded. (Default: POST)
This option can be overridden individually for each web service configuration.
static.webservice.
request_timeout
integrity If a web service request is not answered by the web service instance after this time (milliseconds), the ClassiX instance is terminated and replaced by another one. (Default: 1 minute)
static.webservice.
request_queue_limit
integrity The maximum number of unprocessed web service requests that will be collected before the next incoming requests are rejected with an error message. (Default: 100)
static.webservice.
request_receive_timeout
4.6.1
integrity The maximum time (in milliseconds) that an HTTP client has to transfer a web service request to the server before the connection is simply closed to protect against DoS attacks. (Default: 30 seconds)
static.webservice.
request_receive_max_size
4.6.1
integrity The maximum amount of data (in bytes) that an HTTP client may transfer to the server in a web service request before the connection is simply closed to protect against DoS attacks. (Default: 10 MB)
static.webservice.
request_preload_limit
4.6.1
integrity The maximum number of requests that are pre-buffered in parallel per web service configuration. This option only needs to be adjusted if there are a lot of very slow HTTP clients that take several seconds to transmit their request and thus hinder faster HTTP clients whose request cannot be processed during this time. (Default: 10)
static.webservice.
registration_webservice
4.2.0
string The web service to be triggered at a ClassiX instance as soon as the ClassiX is reserved as web service instance. The web service configuration is transmitted in this web service request in the body. Can be set to false to not trigger a web service. (Default: "webservice_assigned")

Advanced Web Service Configuration

4.2.0

With web services tasks of varying degrees of complexity, it sometimes makes sense to distribute the tasks to different instances. For example, in order to ensure that users can always log in (duration: < 1 second) and at the same time perform complex calculations via a web service (duration: > 1 minute), it is not sufficient to simply increase the number of web service instances(static.webservice.instances). If there are enough long running requests, then all web services are blocked and no user can log in.
To solve the problem, you can now configure in detail how many instances should be responsible for what. For this purpose, instead of a number, an array of objects is set in static.webservice.instances, which indicates how many instances are responsible for which webservice configuration. Each configuration has its own queue for pending webservice requests.

The objects have the following format:

Field Type Description
allowCORS
4.10.7
booleanIf true, then the web service requests of this path configuration also work via CORS.
(Default: false)
id
4.5.4
string A short name used to identify the configuration in logs and in the Admin Console.
If an id is set, then this ID is also displayed in the admin console for WebService-ClassiX instances, so that you can see at a glance which instance is assigned to which configuration.

If no id is defined, no additional information is displayed in the Admin Console.
instances integrity The number of instances to be reserved for this path. (Default: 1)
launch
4.6.0
Object

If this object is set, then the web service configuration becomes an actively starting configuration and actively starts its own instances via the launcher as dedicated ClassiX instances with the specified parameters, which are then directly assigned to this configuration.

In this way, a MorphIT installation for different web services can simply start different ClassiX products that implement only this web service. This has the advantage that the main application can remain lean and does not have to implement the required web services and, conversely, the web service application can remain lean because it is never used as an interactive instance.

The object specified here corresponds to the object that is also passed with the server command launch_dedicated_classix and supports the fields:

  • cmd
  • host (default: "localhost")
  • config.cwd (default: ws.launcher.launch.cwd)
  • config.env (default: {} = no environment variables)

Since these instances are started via the mechanism of the dedicated ClassiX instances, they also receive the system role dedicated instead of prelaunched. Since the instances are started as dedicated instances, these web service instances are also started if launch_strategy.enabled=false is set.

methods
4.12.0
ArraySpecifies which HTTP methods (GET, POST, PUT, DELETE, ...) should be forwarded to the ClassiX webservice instances of this configuration. (Default: static.webservice.methods)
path string | Array

A path (starting with '/') or array of web service paths to which this definition applies. Regular expressions can be used here (except ^ and $, which are automatically added). The expression must match the entire path and can also optionally match the query string. Can be set to an empty string to not accept any queries.

The field is optional. (Default: ".*")

4.4.3 the path is case-insensitive. In all previous versions it is case sensitive.

registration_webservice string The Web service to be triggered on this instance once it has been assigned as a Web service instance of this configuration. This path can contain query parameters. Default is the value from: static.webservice.registration_webservice
The associated configuration object is sent in the body of the web service request.
Can be set to false to prevent a web service from being triggered.

Example:

"instances":[ { "id":"login", "path":"/login" }, { "path":"/long_running_task", "launch": { "cmd":"task_webservice_runner.bat" } }, { "id":"default", "instances":2 }, { "id":"worker", "parameters":["do","important","work!"], "instances":2, "registration_webservice":"start_worker" } ]

Here 6 web service instances are defined:

  • The first instance is only responsible for the login and ensures that someone can log in at any time.
  • The second instance is only responsible for long-term tasks. This way, long-running tasks do not burden the login process.
    This instance starts a dedicated ClassiX instance via the specified start command, which is assigned to this configuration.
  • Two instances process the remaining web service requests (default path is ".*").
  • The last two instances do not receive web service requests from outside and work as worker processes in the background.

The paths are evaluated from top to bottom, so a wildcard path (".*") ensures that subsequent definitions do not process web service requests, even if they explicitly define a path.

In the example, the web service /start_worker is triggered (message: START_WORKER_POST) for the worker instances during registration and the following JSON is transmitted in the body:

{ "id":"worker", "parameters":["do","important","work!"], "instances":2, "registration_webservice":"start_worker" }

The field parameters was only set here to illustrate that any fields can be set in the web service configuration and then transferred to the web service instance during registration. The registration web service can be used to initialize the web service processes (preload cache, start timed trigger, ...).

Safety aspects

Access to all static pages

In the dynamic MorphIT mode there is a standing WebSocket connection which cannot be manipulated by third parties and cannot be read if SSL is activated. ClassiX can therefore decide at each event which view the user sees. In static mode ClassiX does not have this control, because no ClassiX is involved in the delivery of the views (performance) and there is no standing connection to the clients, because HTTP is basically stateless.

This is also intended in order to be able to serve as many clients as possible with as few resources as possible. Consequently, this also means that every static view that has been exported can be called up by any client. Cookies are not used in static mode.

Since the static views should only be used before login, it should not matter that ClassiX has no control over the delivery and order of the views.

Web service requests

The web service requests can, like the web sockets, also be secured by SSL and are thus protected against being read by third parties. Parameters are always transferred in the body of the HTTP POST requests. The processing of the requests should be implemented in ClassiX stateless, so that unfavourable combinations of requests do not lead to unexpected results.

The MorphIT server has a configurable request queue length (default: 100), which means that if there are too many requests on the server, new requests are rejected with an error message. This means that the server is immediately available again after a DOS attack as soon as the requests from the request queue have been processed. Several incoming requests are completely loaded into the server in parallel before they are assigned to a ClassiX instance, so that slower HTTP clients do not slow down the faster clients. Requests that are too large or too slow are aborted by the server.

By using CATPCHAs in static mode, spamming and DOS attacks can be further limited. The MorphIT server knows which web services require a CAPTCHA and these are directly rejected by the server if the CAPTCHA is missing or invalid.

Login

Essential for the static mode is the protection of the change to the dynamic mode (login). It must be ensured that no unauthenticated user can access a ClassiX instance (web service instance excluded). This is ensured as follows:

The server generally does not accept web socket connections from MorphIT clients. Only when a ClassiX instance responds to a web service request of the client with a login message, the server generates a random endpoint ID, at which the client can establish a web socket connection with the server. The ID is a randomly generated SHA-256 hash and therefore not trivial to guess. This ID is only given to the client once in the web service response and is only valid for exactly one connection setup. Since the client establishes the connection immediately after the login response and the endpoint ID becomes invalid again, reading the ID is of no use to an attacker.

In the event that the client does not establish the connection because the tab is closed at that exact moment, the ID expires after a configurable time (default: 1 minute), so that unused IDs do not accumulate and can potentially be guessed. As soon as the client has established the connection, his communication with the instance is tap-proof and protected against manipulation by the SSL (optional, recommended) web socket.

Due to the stateless HTTP protocol, it cannot be ruled out that a malicious client may use a different login token for login than the one returned to it by the web service, because it "knows" wherever it is from. So the client could log in with user A to reserve a web socket and then give the assigned ClassiX instance the login token of user B (which he somehow got hold of). So if the attacker knows the login token of an administrator and the access data of a normal user, he can log in as administrator. To prevent such an attack scenario, the following guidelines apply for the implementation of the login process in ClassiX:

  • ClassiX may not allow a user to access the login tokens of other users.
  • The login process in the ClassiX system must ensure that the issued login token is valid only once.
  • A login token must lose its validity after a certain time if it is not used.

Direct links

It also applies to direct links that no unauthorised user may access a ClassiX instance. Since direct links cannot be processed statically, a ClassiX instance has to process them. To make sure that a malicious client is not assigned a ClassiX instance with an arbitrary (invalid) direct link, the direct link must first be validated by the web service instance(VALIDATE_AUTO_POST). Invalid links will be rejected in this way and will not receive a connection. Valid links are authenticated via the login mechanism and the connection is established in the same way.

WebWidgets

WebWidgets work in static mode also via HTTP-POST requests and are processed by the WebService instance. Again, no standing connection to a ClassiX instance is required.